package com.wxy.demo.tencent_cos;


import com.alibaba.fastjson.JSONObject;
import com.qcloud.cos.COSClient;
import com.qcloud.cos.ClientConfig;
import com.qcloud.cos.auth.BasicCOSCredentials;
import com.qcloud.cos.auth.BasicSessionCredentials;
import com.qcloud.cos.auth.COSCredentials;
import com.qcloud.cos.auth.COSSigner;
import com.qcloud.cos.exception.CosClientException;
import com.qcloud.cos.http.HttpMethodName;
import com.qcloud.cos.http.HttpProtocol;
import com.qcloud.cos.model.*;
import com.qcloud.cos.region.Region;
import com.qcloud.cos.utils.IOUtils;
import com.tencent.cloud.CosStsClient;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.Date;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;

/**
 * cos 腾讯云对象存储工具类-带认证
 * PS:
 * COSClient 是线程安全的类，允许多线程访问同一实例。因为实例内部维持了一个连接池，创建多个实例可能导致程序资源耗尽，请确保程序生命周期内实例只有一个，
 * 并在不再需要使用时，调用 shutdown 方法将其关闭。如果需要新建实例，请先将之前的实例关闭。
 *
 * @author apple
 */
@Slf4j
public class TencentCosUtil {
    private static COSClient cosClient = null;

    /**超时时间设置  单位秒*/
    private static final Integer COS_REQUEST_TIME_OUT = 120;

    private static final String STR_SPLIT_QUESTION = "?";

    private final CosConfig config = new CosConfig();


    /**
     * 获取cos客服端 单实例获取
     *
     * @return
     */
    public synchronized COSClient getCosClient() {
        if (cosClient == null) {
            COSCredentials cred = new BasicCOSCredentials(config.getSecretId(), config.getSecretKey());
            Region region = new Region(config.getRegion());
            ClientConfig clientConfig = new ClientConfig(region);
            //访问协议,支持https http，默认http 目前方便测试使用 采用http
            clientConfig.setHttpProtocol(HttpProtocol.http);
            // 设置 socket 读取超时，默认 30s
            clientConfig.setSocketTimeout(COS_REQUEST_TIME_OUT * 1000);
            // 设置建立连接超时，默认 30s
            clientConfig.setConnectionTimeout(COS_REQUEST_TIME_OUT * 1000);
            cosClient = new COSClient(cred, clientConfig);
        }
        return cosClient;
    }

    /**
     * 关闭cos实例
     */
    public void closeCosClient() {
        if (cosClient != null) {
            cosClient.shutdown();
            cosClient = null;
        }
    }

    /**
     * 创建存储桶
     * 存储桶名称，格式：BucketName-APPID
     *
     * @param bucketName
     */
    public Bucket createBucket(String bucketName) {
        CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName);
        // // 设置 bucket 的权限为 Private(私有读写)、其他可选有 PublicRead（公有读私有写）、PublicReadWrite（公有读写）
        createBucketRequest.setCannedAcl(CannedAccessControlList.Private);
        try {
            return getCosClient().createBucket(createBucketRequest);
        } catch (CosClientException e) {
            log.error("========创建桶发生异常=========", e);
            return null;
        }
    }

    /**
     * 获取用户桶列表
     *
     * @return
     */
    public Map<String, String> getBucketList() {
        List<Bucket> list = getCosClient().listBuckets();
        if (list == null) {
            log.info("=====用户桶列表为空======");
            return new HashMap<>();
        }

        Map<String, String> result = new HashMap<>();
        list.forEach(bucket -> result.put(bucket.getName(), bucket.getLocation()));
        return result;
    }

    /**
     * 上传文件到cos
     *
     * @param filePath   要上传的文件路径
     * @param uploadPath 文件保存路径，可以为空
     * @param bucketName 保存桶名称
     * @return
     */
    public PutObjectResult uploadFile(String filePath, String uploadPath, String bucketName) {
        File localFile = new File(filePath);
        // uploadPath指定文件上传到 COS 上的路径，即对象键。例如对象键为folder/picture.jpg，则表示将文件 picture.jpg 上传到 folder 路径下
        PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, uploadPath, localFile);
        try {
            return getCosClient().putObject(putObjectRequest);
        } catch (CosClientException e) {
            log.error("=======上传文件发生异常=======", e);
            return null;
        }
    }

    public PutObjectResult uploadFile(String bucketName, String uploadPath, File localFile) {
        PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, uploadPath, localFile);
        try {
            return getCosClient().putObject(putObjectRequest);
        } catch (CosClientException e) {
            log.error("=======上传文件发生异常=======", e);
            return null;
        }
    }

    public String uploadFile(MultipartFile file, String subPath) {
        Path localFile = null;
        try {
            String fileName = file.getOriginalFilename();
            String suffix = StringUtils.isBlank(fileName)?"":fileName.substring(fileName.lastIndexOf("."));
            // 上传的文件临时存到本地
            localFile = Files.createTempFile(String.valueOf(System.currentTimeMillis()), suffix);
            file.transferTo(localFile);
            // 指定要上传到 COS 上对象键
            String path = UUID.randomUUID().toString();
            // https://tce-1304962210.cos.ap-chongqing.myqcloud.com/tce-kbms/名字+随机.jpg
            String key = new StringJoiner("").add(config.getRootPath()).add(subPath).add(path.replaceAll("-","")).add("_").add(fileName).toString();
            uploadFile(config.getBucketName(), key, localFile.toFile());

            return config.getDomain() + key;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new RuntimeException("COS：上传文件失败");
        } finally {
            if (null != localFile) {
                try {
                    Files.delete(localFile);
                } catch (IOException e) {
                    log.error("COS上传文件，删除本地临时文件失败={}",e);
                }
            }
        }
    }

    /**
     * 通过临时私密获取COSClient
     * @return
     */
    public COSClient getCosClientByTmpSign() {
        CosSessionToken cosSessionToken = createSessionToken();
        COSCredentials cred = new BasicSessionCredentials(cosSessionToken.getCredentials().getTmpSecretId(),
                cosSessionToken.getCredentials().getTmpSecretKey(), cosSessionToken.getCredentials().getSessionToken());

        // ClientConfig 中包含了后续请求 COS 的客户端设置：
        ClientConfig clientConfig = new ClientConfig();

        // 设置 bucket 的地域
        // COS_REGION 请参照 https://cloud.tencent.com/document/product/436/6224
        clientConfig.setRegion(new Region(config.getRegion()));

        // 设置请求协议, http 或者 https
        // 5.6.53 及更低的版本，建议设置使用 https 协议
        // 5.6.54 及更高版本，默认使用了 https
        clientConfig.setHttpProtocol(HttpProtocol.https);

        // 以下的设置，是可选的：

        // 设置 socket 读取超时，默认 30s
        clientConfig.setSocketTimeout(COS_REQUEST_TIME_OUT * 1000);
        // 设置建立连接超时，默认 30s
        clientConfig.setConnectionTimeout(COS_REQUEST_TIME_OUT * 1000);

        // 生成 cos 客户端。
        return new COSClient(cred, clientConfig);
    }

    public CosSessionToken createSessionToken() {
        TreeMap<String, Object> configTmp = new TreeMap<>();

        try {
            //这里的 SecretId 和 SecretKey 代表了用于申请临时密钥的永久身份（主账号、子账号等），子账号需要具有操作存储桶的权限。
            // 替换为您的云 api 密钥 SecretId
            configTmp.put("secretId", config.getSecretId());
            // 替换为您的云 api 密钥 SecretKey
            configTmp.put("secretKey", config.getSecretKey());

            // 设置域名:
            // 如果您使用了腾讯云 cvm，可以设置内部域名
            //config.put("host", "sts.internal.tencentcloudapi.com");

            // 临时密钥有效时长，单位是秒，默认 1800 秒，目前主账号最长 2 小时（即 7200 秒），子账号最长 36 小时（即 129600）秒
            configTmp.put("durationSeconds", 3600);

            // 换成您的 bucket
            configTmp.put("bucket", config.getBucketName());
            // 换成 bucket 所在地区
            configTmp.put("region", config.getRegion());

            // 这里改成允许的路径前缀，可以根据自己网站的用户登录态判断允许上传的具体路径
            // 列举几种典型的前缀授权场景：
            // 1、允许访问所有对象："*"
            // 2、允许访问指定的对象："a/a1.txt", "b/b1.txt"
            // 3、允许访问指定前缀的对象："a*", "a/*", "b/*"
            // 如果填写了“*”，将允许用户访问所有资源；除非业务需要，否则请按照最小权限原则授予用户相应的访问权限范围。
            configTmp.put("allowPrefixes", new String[] {"*"
            });

            // 密钥的权限列表。必须在这里指定本次临时密钥所需要的权限。
            // 简单上传、表单上传和分块上传需要以下的权限，其他权限列表请看 https://cloud.tencent.com/document/product/436/31923
            String[] allowActions = new String[] {
                    //简单上传操作
                    "name/cos:PutObject",
                    //表单上传对象
                    "name/cos:PostObject",
                    //分块上传：初始化分块操作
                    "name/cos:InitiateMultipartUpload",
                    //分块上传：List 进行中的分块上传
                    "name/cos:ListMultipartUploads",
                    //分块上传：List 已上传分块操作
                    "name/cos:ListParts",
                    //分块上传：上传分块块操作
                    "name/cos:UploadPart",
                    //分块上传：完成所有分块上传操作
                    "name/cos:CompleteMultipartUpload",
                    //取消分块上传操作
                    "name/cos:AbortMultipartUpload",
                    //下载操作
                    "name/cos:GetObject",
                    //查询桶列表
                    "name/cos:GetService",
                    //创建桶
                    "name/cos:PutBucket",
                    "name/cos:GetBucket",
                    "name/cos:HeadBucket",
                    "name/cos:DeleteBucket"
            };
            configTmp.put("allowActions", allowActions);

            return JSONObject.parseObject(CosStsClient.getCredential(configTmp).toString(), CosSessionToken.class) ;
        } catch (Exception e) {
            log.error("=== 获取临时密钥异常 ===",e);
            throw new IllegalArgumentException("no valid secret !");
        }
    }

    /**
     * 获取cos地址签名  1个小时有效时间
     * @param method
     * @param cosUrl
     * @return
     */
    public String createCosUrlSign(String method, String cosUrl) {
        return new COSSigner().buildAuthorizationStr(getHttpMethod(method),
                cosUrl.substring(config.getDomain().length() -1),
                new BasicCOSCredentials(config.getSecretId(), config.getSecretKey()),
                Date.from(LocalDateTime.now().plusHours(1).atZone(ZoneId.systemDefault()).toInstant()));
    }

    /**
     *
     * @param cosKey
     * @return
     */
    public String createCosUrlSign(String cosKey, Integer timeOutMinute) {
        return new COSSigner().buildAuthorizationStr(HttpMethodName.GET,
                cosKey,
                new BasicCOSCredentials(config.getSecretId(), config.getSecretKey()),
                Date.from(LocalDateTime.now().plusMinutes(timeOutMinute).atZone(ZoneId.systemDefault()).toInstant()));
    }

    private HttpMethodName getHttpMethod(String method) {
        HttpMethodName hm = null;
        switch (method){
            case "post":
                hm = HttpMethodName.POST;
                break;
            case "delete":
                hm = HttpMethodName.DELETE;
                break;
            case "head":
                hm = HttpMethodName.HEAD;
                break;
            default:
                hm = HttpMethodName.GET;
        }
        return hm;
    }

    /**
     * 基于临时密钥的下载方法
     * @param url
     * @return
     * @throws IOException
     */
    public byte[] downLoadFile(String url) throws IOException {
        byte[] bytes = null;
        COSClient cosClient = null;
        String bucketName = url.substring(url.indexOf("/") + 2, url.indexOf("."));

        if (config.getCosSignEnable() || url.contains(STR_SPLIT_QUESTION)) {
            cosClient = getCosClientByTmpSign();
        } else {
            cosClient = getCosClient();
        }

        String key = null;
        if (url.contains(STR_SPLIT_QUESTION)) {
            key = url.substring(url.indexOf(".com") + 4, url.indexOf(STR_SPLIT_QUESTION));
        } else {
            key = url.substring(url.indexOf(".com") + 4);
        }

        GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, key);
        COSObjectInputStream cosObjectInput = null;

        try {
            COSObject cosObject = cosClient.getObject(getObjectRequest);
            cosObjectInput = cosObject.getObjectContent();
            bytes = IOUtils.toByteArray(cosObjectInput);
        } catch (Exception e) {
            log.error("=== 获取cos文件发生异常 ===", e);
            return null;
        } finally {
            if (cosObjectInput != null) {
                cosObjectInput.close();
            }
            if (config.getCosSignEnable() || key.contains("?")) {
                cosClient.shutdown();
            }
        }

        return bytes;
    }

}
